﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using DomainDriver.CommonUtilities.Reflectors;
using DomainDriver.DomainModeling.Attributes;
using DomainDriver.DomainModeling.DataProviders;
using DomainDriver.DomainModeling.DomainObjects;
using DomainDriver.DomainModeling.Queries;

namespace DomainDriver.DomainModeling.StorageManagers
{
    public class TransientStorageManager<KEY, KEYED_DOMAIN_OBJECT> : QueryableStorageManagerBase<KEY, KEYED_DOMAIN_OBJECT>
        where KEYED_DOMAIN_OBJECT : IKeyedDomainObject<KEY, KEYED_DOMAIN_OBJECT>
    {
        protected const string KeyPropertyName = "Key";

        protected Dictionary<KEY, KEYED_DOMAIN_OBJECT> m_ObjectStore;
        protected PropertyInfo m_KeyProperty;
        protected IDictionary<Type, IList<PropertyInfo>> m_DomainObjectPropertiesByAttributeType;

        public TransientStorageManager(IDataProvider dataProvider)
            : base(dataProvider)
        {
            m_ObjectStore = new Dictionary<KEY, KEYED_DOMAIN_OBJECT>();
            m_KeyProperty = ClassReflector.GetPropertyByName(typeof(KEYED_DOMAIN_OBJECT), KeyPropertyName);
            m_DomainObjectPropertiesByAttributeType = ClassReflector.GetPropertiesForAttributeType(typeof(KEYED_DOMAIN_OBJECT));
        }

        protected override IEnumerable<KEYED_DOMAIN_OBJECT> GetQueryableDataStore()
        {
            return m_ObjectStore.Values;
        }

        protected override Func<KEYED_DOMAIN_OBJECT, bool> GetKeyMatchingPredicate(ICollection<KEY> keys)
        {
            Func<KEYED_DOMAIN_OBJECT, bool> keyMatchingPredicate = ((KEYED_DOMAIN_OBJECT domainObject) => keys.Contains(domainObject.Key));
            return keyMatchingPredicate;
        }

        protected override ICollection<KEY> ReadRawKeys(Func<KEYED_DOMAIN_OBJECT, bool> predicate)
        {
            IEnumerable<KEYED_DOMAIN_OBJECT> domainObjects = m_ObjectStore.Values.Where<KEYED_DOMAIN_OBJECT>(predicate);
            List<KEY> matchingKeys = new List<KEY>();

            foreach (KEYED_DOMAIN_OBJECT domainObject in domainObjects)
            {
                matchingKeys.Add(domainObject.Key);
            }
            return matchingKeys;
        }

        protected override ICollection<KEYED_DOMAIN_OBJECT> ReadRawObjects(Func<KEYED_DOMAIN_OBJECT, bool> predicate)
        {
            IEnumerable<KEYED_DOMAIN_OBJECT> domainObjects = m_ObjectStore.Values.Where<KEYED_DOMAIN_OBJECT>(predicate);
            List<KEYED_DOMAIN_OBJECT> matchingObjects = new List<KEYED_DOMAIN_OBJECT>();

            foreach (KEYED_DOMAIN_OBJECT domainObject in domainObjects)
            {
                matchingObjects.Add(domainObject);
            }
            return matchingObjects;
        }

        protected override void WriteObject(KEYED_DOMAIN_OBJECT domainObject, ChangeActionType changeAction)
        {
            if (changeAction == ChangeActionType.Delete)
            {
                m_ObjectStore.Remove(domainObject.Key);
                return;
            }

            Dictionary<KEY, KEYED_DOMAIN_OBJECT> otherDomainObjectsByKey = new Dictionary<KEY, KEYED_DOMAIN_OBJECT>(m_ObjectStore);
            List<KEYED_DOMAIN_OBJECT> otherDomainObjects = new List<KEYED_DOMAIN_OBJECT>(otherDomainObjectsByKey.Values);
            if (changeAction == ChangeActionType.Update)
            {
                otherDomainObjectsByKey.Remove(domainObject.Key);
                otherDomainObjects = new List<KEYED_DOMAIN_OBJECT>(otherDomainObjectsByKey.Values);
            }

            Type uniqueAttributeType = typeof(UniqueAttribute);
            if (m_DomainObjectPropertiesByAttributeType.ContainsKey(uniqueAttributeType))
            {
                IList<PropertyInfo> uniqueProperties = m_DomainObjectPropertiesByAttributeType[uniqueAttributeType];
                AssertUniqueAttribute(domainObject, otherDomainObjects, uniqueProperties);
            }

            Type notNullAttributeType = typeof(NotNullAttribute);
            if (m_DomainObjectPropertiesByAttributeType.ContainsKey(notNullAttributeType))
            {
                IList<PropertyInfo> notNullProperties = m_DomainObjectPropertiesByAttributeType[notNullAttributeType];
                AssertNotNullAttribute(domainObject, notNullProperties);
            }

            Type notEqualToValueAttributeType = typeof(NotEqualToValueAttribute);
            if (m_DomainObjectPropertiesByAttributeType.ContainsKey(notEqualToValueAttributeType))
            {
                IList<PropertyInfo> notEqualToValueProperties = m_DomainObjectPropertiesByAttributeType[notEqualToValueAttributeType];
                AssertNotEqualToValueAttribute(domainObject, notEqualToValueProperties);
            }

            KEYED_DOMAIN_OBJECT copy = domainObject.Copy();

            if (changeAction == ChangeActionType.Create)
            {
                m_ObjectStore.Add(copy.Key, copy);
                return;
            }
            if (changeAction == ChangeActionType.Update)
            {
                m_ObjectStore[copy.Key] = copy;
                return;
            }
        }

        #region Assertion Support

        private void AssertUniqueAttribute(KEYED_DOMAIN_OBJECT domainObject, List<KEYED_DOMAIN_OBJECT> otherDomainObjects, IList<PropertyInfo> uniqueProperties)
        {
            foreach (PropertyInfo propertyInfo in uniqueProperties)
            {
                object currentValue = propertyInfo.GetValue(domainObject, null);

                foreach (KEYED_DOMAIN_OBJECT otherDomainObject in otherDomainObjects)
                {
                    object otherValue = propertyInfo.GetValue(otherDomainObject, null);
                    if (currentValue.Equals(otherValue))
                    {
                        throw new ApplicationException();
                    }
                }
            }
        }

        private void AssertNotNullAttribute(KEYED_DOMAIN_OBJECT domainObject, IList<PropertyInfo> notNullProperties)
        {
            foreach (PropertyInfo propertyInfo in notNullProperties)
            {
                object currentValue = propertyInfo.GetValue(domainObject, null);

                if (currentValue == null)
                {
                    throw new ApplicationException();
                }
            }
        }

        private void AssertNotEqualToValueAttribute(KEYED_DOMAIN_OBJECT domainObject, IList<PropertyInfo> notEqualToValueProperties)
        {
            foreach (PropertyInfo propertyInfo in notEqualToValueProperties)
            {
                Attribute untypedAttribute = propertyInfo.GetCustomAttributes(typeof(NotEqualToValueAttribute), true)[0] as Attribute;
                NotEqualToValueAttribute typedAttribute = untypedAttribute as NotEqualToValueAttribute;
                object currentValue = propertyInfo.GetValue(domainObject, null);

                if (currentValue.Equals(typedAttribute.InvalidValue))
                {
                    throw new ApplicationException();
                }
            }
        }

        #endregion
    }
}